#include "dependency_calculator.h"

#include <algorithm>
#include <list>
#include <cassert>

namespace netaos {
namespace nosal {
namespace exec {
namespace internal {

TransitionChanges DependencyCalculator::CalculateTransition(
    const std::map<std::string, FunctionGroup>& list_of_function_groups,
    const std::set<std::shared_ptr<StartupConfiguration>> executed_startup_configurations,
    const FunctionGroup* function_group,
    const std::string& state)
{
    // Create a complete list of required processes/startup configurations for the specified function_groupStates
    std::set<std::shared_ptr<StartupConfiguration>> all_required_startup_configurations;

    std::vector<std::shared_ptr<StartupConfiguration>> group_startup_configurations
        = function_group->GetStartupConfigurations(state);

    all_required_startup_configurations.insert(group_startup_configurations.cbegin(), group_startup_configurations.cend());

    // Create a list of processes to terminate.
    std::vector<std::shared_ptr<StartupConfiguration>> toTerminate;
    for (std::shared_ptr<StartupConfiguration> startup_configuration : executed_startup_configurations) {
        bool is_startup_configuration_required
            = std::find_if(list_of_function_groups.cbegin(),
                  list_of_function_groups.cend(),
                  [&startup_configuration](const auto& fg_pair) {
                      return fg_pair.second.IsStartupConfigurationRequiredNow(startup_configuration);
                  })
            != list_of_function_groups.cend();

        if (!is_startup_configuration_required) {
            toTerminate.push_back(startup_configuration);
        }
    }

    // Create a list of processes to start.
    std::vector<std::shared_ptr<StartupConfiguration>> toStart;
    for (std::shared_ptr<StartupConfiguration> startup_configuration : all_required_startup_configurations) {

        if (executed_startup_configurations.find(startup_configuration) == executed_startup_configurations.cend()) {
            toStart.push_back(startup_configuration);
        }
    }

    // Prepare the result
    TransitionChanges result;

    result.toStart_.reserve(toStart.size());
    result.toStart_.insert(result.toStart_.cend(), toStart.cbegin(), toStart.cend());
    SortByDependency(result.toStart_);

    result.toTerminate_.reserve(toTerminate.size());
    result.toTerminate_.insert(result.toTerminate_.cend(), toTerminate.cbegin(), toTerminate.cend());
    SortByDependency(result.toTerminate_);
    std::reverse(result.toTerminate_.begin(), result.toTerminate_.end());

    return result;
}

void DependencyCalculator::SortByDependency(
    std::vector<std::shared_ptr<StartupConfiguration>>& list_of_applications)
{
    dependency_summary_.clear();
    for (const auto& application : list_of_applications) {
        const auto result
            = dependency_summary_.insert({application->GetName(), {0, false, application->GetDependencies()}});
        assert(result.second);
    }

    for (const auto& application : list_of_applications) {
        CalcDependencyLength(application->GetName());
    }

    std::sort(list_of_applications.begin(),
        list_of_applications.end(),
        [&](const std::shared_ptr<StartupConfiguration> app1, const std::shared_ptr<StartupConfiguration> app2) {
            return dependency_summary_.at(app1->GetName()).length < dependency_summary_.at(app2->GetName()).length;
        });
}

std::uint32_t DependencyCalculator::CalcDependencyLength(const std::string& process_name)
{
    auto summary_it = dependency_summary_.find(process_name);

    // If there is no such dependency then the dependency process is already running or terminated
    if (dependency_summary_.end() == summary_it) {
        return 0U;
    }

    if (!summary_it->second.is_calculation_finished) {

        std::uint32_t maxLevel = 0;
        for (const auto& dependency_it : summary_it->second.dependencies) {
            const auto l = CalcDependencyLength(dependency_it.first);
            maxLevel = std::max(maxLevel, l);
        }

        summary_it->second.is_calculation_finished = true;
        summary_it->second.length = maxLevel + 1U;
    }

    return summary_it->second.length;
}

}  // namespace internal
}  // namespace exec
}  // namespace nosal
}  // namespace netaos
